#ifndef BINARYSTREAM_H
#define BINARYSTREAM_H

#include <cstdint>
#include <iostream>
#include <fstream>
#include <vector>
#include <functional>
#include "sys/types.h"

#include "serializable.h"
typedef u_int8_t byte;

class BinaryStream {

    public:
        BinaryStream(std::fstream& fileStream, u_int32_t size, bool illimited = false);
        virtual ~BinaryStream();

        void checkPosition(bool reading, u_int32_t sizeData);
        void load();
        void flush();

        template<typename Primitive> Primitive readPrimitive();
        template<typename Primitive> void writePrimitive(Primitive value);

        std::string readString();
        void writeString(std::string const str);

        template<typename Primitive> std::vector<Primitive> readPrimitives();
        template<typename Primitive> void writePrimitives(std::vector<Primitive> value);

        template<class Class>
        std::vector<Class> readVector();

        template<class Class>
        void writeVector(std::vector<Class> const vector);

        static u_int32_t sizeOf(Serializable& serializable){
            std::fstream fs; //Para que el compilador no moleste.
            BinaryStream stream(fs, 0);
            serializable.write(stream);
            return stream.pos;
        }

    private:
        std::fstream& fileStream;
        u_int32_t size;
        bool illimited;
        u_int32_t pos;
        byte* buffer;
};

template<typename Primitive>
Primitive BinaryStream::readPrimitive(){
    Primitive value;
    u_int32_t sizeData = sizeof(Primitive);
    checkPosition(true, sizeData);
    value = *((Primitive*) &buffer[pos]);
    pos += sizeData;
    return value;
}

template<typename Primitive>
void BinaryStream::writePrimitive(Primitive value){
    u_int32_t sizeData = sizeof(Primitive);
    checkPosition(false, sizeData);
    if (buffer)
        *((Primitive*) &buffer[pos]) = value;
    pos += sizeData;
}

template<typename Primitive>
std::vector<Primitive> BinaryStream::readPrimitives(){
    u_int32_t size = readPrimitive<u_int16_t>();
    std::vector<Primitive> vector;
    for (u_int32_t i = 0; i < size; i++) {
        vector.push_back(readPrimitive<Primitive>());
    }
    return vector;
}

template<typename Primitive>
void BinaryStream::writePrimitives(std::vector<Primitive> vector){
    writePrimitive<u_int16_t>(vector.size());
    for (Primitive primitive : vector){
        writePrimitive<Primitive>(primitive);
    }
}

template<class Class>
std::vector<Class> BinaryStream::readVector(){
    u_int32_t size = readPrimitive<u_int16_t>();
    std::vector<Class> vector;
    for (u_int32_t i = 0; i < size; i++) {
        Class serializable;
        serializable.read(*this);
        vector.push_back(serializable);
    }
    return vector;
}

template<class Class>
void BinaryStream::writeVector(std::vector<Class> const vector){
    writePrimitive<u_int16_t>(vector.size());
    for (Class serializable : vector){
        serializable.write(*this);
    }
}

#endif // BINARYSTREAM_H
